package org.bookshare.document.beans;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.ArrayUtils;
import org.benetech.exception.WrappedException;

/**
 * The type of document.
 * @author Reuben Firmin
 */
public abstract class DocumentType {

	/**
	 * DocumentType of type DAISY.
	 * @author Reuben Firmin
	 */
	public static final class DAISY extends DocumentType {
		protected DAISY() {
			super("daisy",
					DocumentComponent.XML, DocumentComponent.OPF, DocumentComponent.NCX, DocumentComponent.SMIL,
					DocumentComponent.IMAGE);
		}
	}

	/**
	 * DocumentType of type NIMAS.
	 * @author Reuben Firmin
	 */
	public static final class NIMAS extends DocumentType {
		protected NIMAS() {
			super("nimas",
					DocumentComponent.XML, DocumentComponent.OPF, DocumentComponent.IMAGE, DocumentComponent.PDF);
		}
	}

	/**
	 * DocumentType of type OEB.
	 * @author Reuben Firmin
	 */
	public static final class OEB extends DocumentType {
		protected OEB() {
			super("oeb",
					DocumentComponent.HTML, DocumentComponent.OPF, DocumentComponent.IMAGE);
		}
	}

	/**
	 * DocumentType of type RTF.
	 * @author Reuben Firmin
	 */
	public static final class RTF extends DocumentType {
		protected RTF() {
			super("rtf",
					DocumentComponent.RTF);
		}
	}

	public static final DocumentType DAISY = new DAISY();
	public static final DocumentType NIMAS = new NIMAS();
	public static final DocumentType OEB = new OEB();
	public static final DocumentType RTF = new RTF();
	private static Map<String, DocumentType> TYPES_BY_NAME = new HashMap<String, DocumentType>();

	private DocumentComponent[] components;
	private String name;

	/**
	 * Private constructor.
	 * @param components Never null
	 */
	private DocumentType(final String name, final DocumentComponent... components) {
		this.components = components;
		this.name = name;
	}

	/**
	 * The components that make up this documenttype.
	 * @return never null
	 */
	public DocumentComponent[] getComponents() {
		return components;
	}

	/**
	 * True if the type contains this component.
	 * @param component Never null
	 * @return Never null
	 */
	public boolean containsComponent(final DocumentComponent component) {
		return ArrayUtils.contains(components, component);
	}

	/**
	 * Get the name of this type.
	 * @return Never null
	 */
	public String getName() {
		return name;
	}

	/**
	 * Return the document type with the given name, if it exists.
	 * @param name The name of the documenttype
	 * @return Never null
	 */
	public static DocumentType getDocumentTypeByName(final String name) {
		if (TYPES_BY_NAME.size() == 0) {
			try {
				final Class[] classes = DocumentType.class.getClasses();
				for (Class clazz : classes) {
					final DocumentType dt = (DocumentType) clazz.newInstance();
					TYPES_BY_NAME.put(dt.getName(), dt);
				}
			} catch (InstantiationException e) {
				throw new WrappedException(e);
			} catch (IllegalAccessException e) {
				throw new WrappedException(e);
			}
		}
		return TYPES_BY_NAME.get(name.toLowerCase());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
    public int hashCode() {
	    final int PRIME = 31;
	    int result = 1;
	    result = PRIME * result + Arrays.hashCode(components);
	    result = PRIME * result + ((name == null) ? 0 : name.hashCode());
	    return result;
    }

	/**
	 * {@inheritDoc}
	 */
	@Override
    public boolean equals(Object obj) {
	    if (this == obj) {
		    return true;
	    }
	    if (obj == null) {
		    return false;
	    }
	    if (getClass() != obj.getClass()) {
		    return false;
	    }
	    final DocumentType other = (DocumentType) obj;
	    if (!Arrays.equals(components, other.components)) {
		    return false;
	    }
	    if (name == null) {
		    if (other.name != null) {
			    return false;
		    }
	    } else if (!name.equals(other.name)) {
		    return false;
	    }
	    return true;
    }
}
